package math.vectorSpace.R3;

import java.awt.Graphics2D;
import math.vectorSpace.R3.visualization.Visualizable;
import math.vectorSpace.R3.visualization.Render3D;
import math.vectorSpace.R3.visualization.VisualizationProperties;

public class Segment3D implements Visualizable {

    private Point3D start, end;

    /**
     * Constructs a segment from Point3D start to Point3D end.
     * 
     * @param start
     * @param end
     */
    public Segment3D(Point3D start, Point3D end) {
        this.start = start;
        this.end = end;
    }

    /**
     * Constructs a segment from coordinates that define the point1 point and point2 point
     * 
     * @param sX the x-coordinate of the point1 point
     * @param sY the y-coordinate of the point1 point
     * @param sZ the z-coordinate of the point1 point
     * @param eX the x-coordinate of the point2 point
     * @param eY the y-coordinate of the point2 point
     * @param eZ the z-coordinate of the point2 point
     */
    public Segment3D(double sX, double sY, double sZ, double eX, double eY,
            double eZ) {
        this.start = new Point3D(sX, sY, sZ);
        this.end = new Point3D(eX, eY, eZ);
    }

    public Point3D getStart() {
        return start;
    }

    public Point3D getEnd() {
        return end;
    }

    public void setStart(Point3D point) {
        start = point;
    }

    public void setEnd(Point3D point) {
        end = point;
    }

    public Line3D convertToLine() {
        return new Line3D(start, end);
    }

    public Segment3D shift(Point3D point) {
        return new Segment3D(start.add(point), end.add(point));
    }

    public Segment3D shift(Vector3D vector) {
        return new Segment3D(start.add(vector), end.add(vector));
    }

    public boolean withinBound(Point3D point) {
        double xMax, xMin, yMax, yMin, zMax, zMin;
        if (start.getX() > end.getX()) {
            xMax = start.getX();
            xMin = end.getX();
        } else {
            xMax = end.getX();
            xMin = start.getX();
        }

        if (start.getY() > end.getY()) {
            yMax = start.getY();
            yMin = end.getY();
        } else {
            yMax = end.getY();
            yMin = start.getY();
        }

        if (start.getZ() > end.getZ()) {
            zMax = start.getZ();
            zMin = end.getZ();
        } else {
            zMax = end.getZ();
            zMin = start.getZ();
        }

        if ((point.getX() < xMax) && (point.getX() > xMin)
                && (point.getY() < yMax) && (point.getY() > yMin)
                && (point.getZ() < zMax) && (point.getZ() > zMin)) {
            return true;
        }
        return false;
    }

    /**
     * Returns the shortest distance from a given point to this segment
     * 
     * @param point
     * @return
     */
    public double distance(Point3D point) {
        /*
         *  Finds the closest point of approach between the point and a line that contains this segment
         *  And check to see if that point is defined within the segment
         *  If it is, then find the distance using the same method as finding the distance from a line to a point
         *  If not, then find the distances from the point2 points, and return the smaller of the two
         */
        if (this.withinBound((point.project(this.convertToLine())))) {
            return this.convertToLine().distance(point);
        } else {
            return Math.min(point.distance(end), point.distance(start));
        }

    }

    /**
     * Returns the shortest distance from a given line to this segment.
     * @param line
     * @return
     */
    //public double distance(Line3D line) {
    //}
    /**
     * Returns the shortest distance from a given segment to this segment.
     * @param segment
     * @return
     */
    //public double distance(Segment3D segment) { 
    //}
    /**
     * Projects this segment onto the surface of the given plane
     * 
     * @param plane a {@link Plane3D} object
     * @return a Segment3D object representing the projection of this
     *         segment onto the given plane
     */
    public Segment3D project(Plane3D plane) {
        Point3D point1 = start.project(plane);
        Point3D point2 = end.project(plane);
        return new Segment3D(point1, point2);
    }

    /**
     * Checks to see if a line intersects this line segment.
     * The intersection point must be defined within the line segment.
     * Returns null if no solution is found.
     * 
     * @param line 
     * @return 
     */
    public Point3D intersect(Line3D line) {
        Point3D intersectPoint = line.intersect(this.convertToLine());
        if (withinBound(intersectPoint)) {
            return intersectPoint;
        } else {
            return null;
        }
    }

    public Point3D intersect(Segment3D s2) {
        Point3D intersect = this.convertToLine().intersect(s2.convertToLine());
        if (this.withinBound(intersect) && s2.withinBound(intersect)) {
            return intersect;
        }
        return null;

    }

    @Override
    public void draw(Render3D render, Graphics2D graphics,
            VisualizationProperties properties) {
        /*
        graphics.setColor(properties.getOutlineColor());
        Graphics2DExtended.drawLine(graphics, render.getScreenHeight(), 
                render.project(start), render.project(end));*/
    }
}
